/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.1 Module
 * -------------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief gl_HelperInvocation tests.
 *//*--------------------------------------------------------------------*/

#include "es31fShaderHelperInvocationTests.hpp"

#include "gluObjectWrapper.hpp"
#include "gluShaderProgram.hpp"
#include "gluDrawUtil.hpp"
#include "gluPixelTransfer.hpp"

#include "glwFunctions.hpp"
#include "glwEnums.hpp"

#include "tcuTestLog.hpp"
#include "tcuVector.hpp"
#include "tcuSurface.hpp"

#include "deUniquePtr.hpp"
#include "deStringUtil.hpp"
#include "deRandom.hpp"
#include "deString.h"

namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace
{

using glu::ShaderProgram;
using tcu::TestLog;
using tcu::Vec2;
using tcu::IVec2;
using de::MovePtr;
using std::string;
using std::vector;

enum PrimitiveType
{
	PRIMITIVETYPE_TRIANGLE = 0,
	PRIMITIVETYPE_LINE,
	PRIMITIVETYPE_WIDE_LINE,
	PRIMITIVETYPE_POINT,
	PRIMITIVETYPE_WIDE_POINT,

	PRIMITIVETYPE_LAST
};

static int getNumVerticesPerPrimitive (PrimitiveType primType)
{
	switch (primType)
	{
		case PRIMITIVETYPE_TRIANGLE:	return 3;
		case PRIMITIVETYPE_LINE:		return 2;
		case PRIMITIVETYPE_WIDE_LINE:	return 2;
		case PRIMITIVETYPE_POINT:		return 1;
		case PRIMITIVETYPE_WIDE_POINT:	return 1;
		default:
			DE_ASSERT(false);
			return 0;
	}
}

static glu::PrimitiveType getGluPrimitiveType (PrimitiveType primType)
{
	switch (primType)
	{
		case PRIMITIVETYPE_TRIANGLE:	return glu::PRIMITIVETYPE_TRIANGLES;
		case PRIMITIVETYPE_LINE:		return glu::PRIMITIVETYPE_LINES;
		case PRIMITIVETYPE_WIDE_LINE:	return glu::PRIMITIVETYPE_LINES;
		case PRIMITIVETYPE_POINT:		return glu::PRIMITIVETYPE_POINTS;
		case PRIMITIVETYPE_WIDE_POINT:	return glu::PRIMITIVETYPE_POINTS;
		default:
			DE_ASSERT(false);
			return glu::PRIMITIVETYPE_LAST;
	}
}

static void genVertices (PrimitiveType primType, int numPrimitives, de::Random* rnd, vector<Vec2>* dst)
{
	const bool	isTri					= primType == PRIMITIVETYPE_TRIANGLE;
	const float	minCoord				= isTri ? -1.5f : -1.0f;
	const float	maxCoord				= isTri ? +1.5f : +1.0f;
	const int	numVerticesPerPrimitive	= getNumVerticesPerPrimitive(primType);
	const int	numVert					= numVerticesPerPrimitive*numPrimitives;

	dst->resize(numVert);

	for (size_t ndx = 0; ndx < dst->size(); ndx++)
	{
		(*dst)[ndx][0] = rnd->getFloat(minCoord, maxCoord);
		(*dst)[ndx][1] = rnd->getFloat(minCoord, maxCoord);
	}

	// Don't produce completely or almost completely discardable primitives.
	// \note: This doesn't guarantee that resulting primitives are visible or
	//        produce any fragments. This just removes trivially discardable
	//        primitives.
	for (int primitiveNdx = 0; primitiveNdx < numPrimitives; ++primitiveNdx)
	for (int component = 0; component < 2; ++component)
	{
		bool negativeClip = true;
		bool positiveClip = true;

		for (int vertexNdx = 0; vertexNdx < numVerticesPerPrimitive; ++vertexNdx)
		{
			const float p = (*dst)[primitiveNdx * numVerticesPerPrimitive + vertexNdx][component];
			// \note 0.9 instead of 1.0 to avoid just barely visible primitives
			if (p > -0.9f)
				negativeClip = false;
			if (p < +0.9f)
				positiveClip = false;
		}

		// if discardable, just mirror first vertex along center
		if (negativeClip || positiveClip)
		{
			(*dst)[primitiveNdx * numVerticesPerPrimitive + 0][0] *= -1.0f;
			(*dst)[primitiveNdx * numVerticesPerPrimitive + 0][1] *= -1.0f;
		}
	}
}

static int getInteger (const glw::Functions& gl, deUint32 pname)
{
	int v = 0;
	gl.getIntegerv(pname, &v);
	GLU_EXPECT_NO_ERROR(gl.getError(), "glGetIntegerv()");
	return v;
}

static Vec2 getRange (const glw::Functions& gl, deUint32 pname)
{
	Vec2 v(0.0f);
	gl.getFloatv(pname, v.getPtr());
	GLU_EXPECT_NO_ERROR(gl.getError(), "glGetFloatv()");
	return v;
}

static void drawRandomPrimitives (const glu::RenderContext& renderCtx, deUint32 program, PrimitiveType primType, int numPrimitives, de::Random* rnd)
{
	const glw::Functions&			gl				= renderCtx.getFunctions();
	const float						minPointSize	= 16.0f;
	const float						maxPointSize	= 32.0f;
	const float						minLineWidth	= 16.0f;
	const float						maxLineWidth	= 32.0f;
	vector<Vec2>					vertices;
	vector<glu::VertexArrayBinding>	vertexArrays;

	genVertices(primType, numPrimitives, rnd, &vertices);

	vertexArrays.push_back(glu::va::Float("a_position", 2, (int)vertices.size(), 0, (const float*)&vertices[0]));

	gl.useProgram(program);

	// Special state for certain primitives
	if (primType == PRIMITIVETYPE_POINT || primType == PRIMITIVETYPE_WIDE_POINT)
	{
		const Vec2		range			= getRange(gl, glu::isContextTypeES(renderCtx.getType()) ? GL_ALIASED_POINT_SIZE_RANGE : GL_SMOOTH_POINT_SIZE_RANGE);
		const bool		isWidePoint		= primType == PRIMITIVETYPE_WIDE_POINT;
		const float		pointSize		= isWidePoint ? de::min(rnd->getFloat(minPointSize, maxPointSize), range.y()) : 1.0f;
		const int		pointSizeLoc	= gl.getUniformLocation(program, "u_pointSize");

		gl.uniform1f(pointSizeLoc, pointSize);
	}
	else if (primType == PRIMITIVETYPE_WIDE_LINE)
	{
		const Vec2		range			= getRange(gl, GL_ALIASED_LINE_WIDTH_RANGE);
		const float		lineWidth		= de::min(rnd->getFloat(minLineWidth, maxLineWidth), range.y());

		gl.lineWidth(lineWidth);
	}

	glu::draw(renderCtx, program, (int)vertexArrays.size(), &vertexArrays[0],
			  glu::PrimitiveList(getGluPrimitiveType(primType), (int)vertices.size()));
}

class FboHelper
{
public:
								FboHelper			(const glu::RenderContext& renderCtx, int width, int height, deUint32 format, int numSamples);
								~FboHelper			(void);

	void						bindForRendering	(void);
	void						readPixels			(int x, int y, const tcu::PixelBufferAccess& dst);

private:
	const glu::RenderContext&	m_renderCtx;
	const int					m_numSamples;
	const IVec2					m_size;

	glu::Renderbuffer			m_colorbuffer;
	glu::Framebuffer			m_framebuffer;
	glu::Renderbuffer			m_resolveColorbuffer;
	glu::Framebuffer			m_resolveFramebuffer;
};

FboHelper::FboHelper (const glu::RenderContext& renderCtx, int width, int height, deUint32 format, int numSamples)
	: m_renderCtx			(renderCtx)
	, m_numSamples			(numSamples)
	, m_size				(width, height)
	, m_colorbuffer			(renderCtx)
	, m_framebuffer			(renderCtx)
	, m_resolveColorbuffer	(renderCtx)
	, m_resolveFramebuffer	(renderCtx)
{
	const glw::Functions&	gl			= m_renderCtx.getFunctions();
	const int				maxSamples	= getInteger(gl, GL_MAX_SAMPLES);

	gl.bindRenderbuffer(GL_RENDERBUFFER, *m_colorbuffer);
	gl.renderbufferStorageMultisample(GL_RENDERBUFFER, m_numSamples, format, width, height);
	gl.bindFramebuffer(GL_FRAMEBUFFER, *m_framebuffer);
	gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, *m_colorbuffer);

	if (m_numSamples > maxSamples && gl.checkFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
		throw tcu::NotSupportedError("Sample count exceeds GL_MAX_SAMPLES");

	TCU_CHECK(gl.checkFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);

	if (m_numSamples != 0)
	{
		gl.bindRenderbuffer(GL_RENDERBUFFER, *m_resolveColorbuffer);
		gl.renderbufferStorage(GL_RENDERBUFFER, format, width, height);
		gl.bindFramebuffer(GL_FRAMEBUFFER, *m_resolveFramebuffer);
		gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, *m_resolveColorbuffer);
		TCU_CHECK(gl.checkFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
	}

	GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to create framebuffer");
}

FboHelper::~FboHelper (void)
{
}

void FboHelper::bindForRendering (void)
{
	const glw::Functions& gl = m_renderCtx.getFunctions();
	gl.bindFramebuffer(GL_FRAMEBUFFER, *m_framebuffer);
	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindFramebuffer()");
	gl.viewport(0, 0, m_size.x(), m_size.y());
	GLU_EXPECT_NO_ERROR(gl.getError(), "viewport()");
}

void FboHelper::readPixels (int x, int y, const tcu::PixelBufferAccess& dst)
{
	const glw::Functions&	gl		= m_renderCtx.getFunctions();
	const int				width	= dst.getWidth();
	const int				height	= dst.getHeight();

	if (m_numSamples != 0)
	{
		gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, *m_resolveFramebuffer);
		gl.blitFramebuffer(x, y, width, height, x, y, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
		gl.bindFramebuffer(GL_READ_FRAMEBUFFER, *m_resolveFramebuffer);
	}

	glu::readPixels(m_renderCtx, x, y, dst);
}

enum
{
	FRAMEBUFFER_WIDTH	= 256,
	FRAMEBUFFER_HEIGHT	= 256,
	FRAMEBUFFER_FORMAT	= GL_RGBA8,
	NUM_SAMPLES_MAX		= -1
};

//! Verifies that gl_HelperInvocation is false in all rendered pixels.
class HelperInvocationValueCase : public TestCase
{
public:
							HelperInvocationValueCase	(Context& context, const char* name, const char* description, PrimitiveType primType, int numSamples);
							~HelperInvocationValueCase	(void);

	void					init						(void);
	void					deinit						(void);
	IterateResult			iterate						(void);

private:
	const PrimitiveType		m_primitiveType;
	const int				m_numSamples;

	const int				m_numIters;
	const int				m_numPrimitivesPerIter;

	MovePtr<ShaderProgram>	m_program;
	MovePtr<FboHelper>		m_fbo;
	int						m_iterNdx;
};

HelperInvocationValueCase::HelperInvocationValueCase (Context& context, const char* name, const char* description, PrimitiveType primType, int numSamples)
	: TestCase					(context, name, description)
	, m_primitiveType			(primType)
	, m_numSamples				(numSamples)
	, m_numIters				(5)
	, m_numPrimitivesPerIter	(10)
	, m_iterNdx					(0)
{
}

HelperInvocationValueCase::~HelperInvocationValueCase (void)
{
	deinit();
}

void HelperInvocationValueCase::init (void)
{
	const glu::RenderContext&	renderCtx		= m_context.getRenderContext();
	const glw::Functions&		gl				= renderCtx.getFunctions();
	const int					maxSamples		= getInteger(gl, GL_MAX_SAMPLES);
	const int					actualSamples	= m_numSamples == NUM_SAMPLES_MAX ? maxSamples : m_numSamples;

	m_program = MovePtr<ShaderProgram>(new ShaderProgram(m_context.getRenderContext(),
		glu::ProgramSources()
			<< glu::VertexSource(
				"#version 310 es\n"
				"in highp vec2 a_position;\n"
				"uniform highp float u_pointSize;\n"
				"void main (void)\n"
				"{\n"
				"	gl_Position = vec4(a_position, 0.0, 1.0);\n"
				"	gl_PointSize = u_pointSize;\n"
				"}\n")
			<< glu::FragmentSource(
				"#version 310 es\n"
				"out mediump vec4 o_color;\n"
				"void main (void)\n"
				"{\n"
				"	if (gl_HelperInvocation)\n"
				"		o_color = vec4(1.0, 0.0, 0.0, 1.0);\n"
				"	else\n"
				"		o_color = vec4(0.0, 1.0, 0.0, 1.0);\n"
				"}\n")));

	m_testCtx.getLog() << *m_program;

	if (!m_program->isOk())
	{
		m_program.clear();
		TCU_FAIL("Compile failed");
	}

	m_testCtx.getLog() << TestLog::Message << "Using GL_RGBA8 framebuffer with "
					   << actualSamples << " samples" << TestLog::EndMessage;

	m_fbo = MovePtr<FboHelper>(new FboHelper(renderCtx, FRAMEBUFFER_WIDTH, FRAMEBUFFER_HEIGHT,
											 FRAMEBUFFER_FORMAT, actualSamples));

	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
}

void HelperInvocationValueCase::deinit (void)
{
	m_program.clear();
	m_fbo.clear();
}

static bool verifyHelperInvocationValue (TestLog& log, const tcu::Surface& result, bool isMultiSample)
{
	const tcu::RGBA		bgRef				(0, 0, 0, 255);
	const tcu::RGBA		fgRef				(0, 255, 0, 255);
	const tcu::RGBA		threshold			(1, isMultiSample ? 254 : 1, 1, 1);
	int					numInvalidPixels	= 0;
	bool				renderedSomething	= false;

	for (int y = 0; y < result.getHeight(); ++y)
	{
		for (int x = 0; x < result.getWidth(); ++x)
		{
			const tcu::RGBA	resPix	= result.getPixel(x, y);
			const bool		isBg	= tcu::compareThreshold(resPix, bgRef, threshold);
			const bool		isFg	= tcu::compareThreshold(resPix, fgRef, threshold);

			if (!isBg && !isFg)
				numInvalidPixels += 1;

			if (isFg)
				renderedSomething = true;
		}
	}

	if (numInvalidPixels > 0)
	{
		log << TestLog::Image("Result", "Result image", result);
		log << TestLog::Message << "ERROR: Found " << numInvalidPixels << " invalid result pixels!" << TestLog::EndMessage;
		return false;
	}
	else if (!renderedSomething)
	{
		log << TestLog::Image("Result", "Result image", result);
		log << TestLog::Message << "ERROR: Result image was empty!" << TestLog::EndMessage;
		return false;
	}
	else
	{
		log << TestLog::Message << "All result pixels are valid" << TestLog::EndMessage;
		return true;
	}
}

HelperInvocationValueCase::IterateResult HelperInvocationValueCase::iterate (void)
{
	const glu::RenderContext&		renderCtx	= m_context.getRenderContext();
	const glw::Functions&			gl			= renderCtx.getFunctions();
	const string					sectionName	= string("Iteration ") + de::toString(m_iterNdx+1) + " / " + de::toString(m_numIters);
	const tcu::ScopedLogSection		section		(m_testCtx.getLog(), (string("Iter") + de::toString(m_iterNdx)), sectionName);
	de::Random						rnd			(deStringHash(getName()) ^ deInt32Hash(m_iterNdx));
	tcu::Surface					result		(FRAMEBUFFER_WIDTH, FRAMEBUFFER_HEIGHT);

	m_fbo->bindForRendering();
	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
	gl.clear(GL_COLOR_BUFFER_BIT);

	drawRandomPrimitives(renderCtx, m_program->getProgram(), m_primitiveType, m_numPrimitivesPerIter, &rnd);

	m_fbo->readPixels(0, 0, result.getAccess());

	if (!verifyHelperInvocationValue(m_testCtx.getLog(), result, m_numSamples != 0))
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid pixels found");

	m_iterNdx += 1;
	return (m_iterNdx < m_numIters) ? CONTINUE : STOP;
}

//! Checks derivates when value depends on gl_HelperInvocation.
class HelperInvocationDerivateCase : public TestCase
{
public:
							HelperInvocationDerivateCase	(Context& context, const char* name, const char* description, PrimitiveType primType, int numSamples, const char* derivateFunc, bool checkAbsoluteValue);
							~HelperInvocationDerivateCase	(void);

	void					init							(void);
	void					deinit							(void);
	IterateResult			iterate							(void);

private:
	const PrimitiveType		m_primitiveType;
	const int				m_numSamples;
	const std::string		m_derivateFunc;
	const bool				m_checkAbsoluteValue;

	const int				m_numIters;

	MovePtr<ShaderProgram>	m_program;
	MovePtr<FboHelper>		m_fbo;
	int						m_iterNdx;
};

HelperInvocationDerivateCase::HelperInvocationDerivateCase (Context& context, const char* name, const char* description, PrimitiveType primType, int numSamples, const char* derivateFunc, bool checkAbsoluteValue)
	: TestCase					(context, name, description)
	, m_primitiveType			(primType)
	, m_numSamples				(numSamples)
	, m_derivateFunc			(derivateFunc)
	, m_checkAbsoluteValue		(checkAbsoluteValue)
	, m_numIters				(16)
	, m_iterNdx					(0)
{
}

HelperInvocationDerivateCase::~HelperInvocationDerivateCase (void)
{
	deinit();
}

void HelperInvocationDerivateCase::init (void)
{
	const glu::RenderContext&	renderCtx		= m_context.getRenderContext();
	const glw::Functions&		gl				= renderCtx.getFunctions();
	const int					maxSamples		= getInteger(gl, GL_MAX_SAMPLES);
	const int					actualSamples	= m_numSamples == NUM_SAMPLES_MAX ? maxSamples : m_numSamples;
	const std::string			funcSource		= (m_checkAbsoluteValue) ? ("abs(" + m_derivateFunc + "(value))") : (m_derivateFunc + "(value)");

	m_program = MovePtr<ShaderProgram>(new ShaderProgram(m_context.getRenderContext(),
		glu::ProgramSources()
			<< glu::VertexSource(
				"#version 310 es\n"
				"in highp vec2 a_position;\n"
				"uniform highp float u_pointSize;\n"
				"void main (void)\n"
				"{\n"
				"	gl_Position = vec4(a_position, 0.0, 1.0);\n"
				"	gl_PointSize = u_pointSize;\n"
				"}\n")
			<< glu::FragmentSource(string(
				"#version 310 es\n"
				"out mediump vec4 o_color;\n"
				"void main (void)\n"
				"{\n"
				"	highp float value		= gl_HelperInvocation ? 1.0 : 0.0;\n"
				"	highp float derivate	= ") + funcSource + ";\n"
				"	if (gl_HelperInvocation)\n"
				"		o_color = vec4(1.0, 0.0, derivate, 1.0);\n"
				"	else\n"
				"		o_color = vec4(0.0, 1.0, derivate, 1.0);\n"
				"}\n")));

	m_testCtx.getLog() << *m_program;

	if (!m_program->isOk())
	{
		m_program.clear();
		TCU_FAIL("Compile failed");
	}

	m_testCtx.getLog() << TestLog::Message << "Using GL_RGBA8 framebuffer with "
					   << actualSamples << " samples" << TestLog::EndMessage;

	m_fbo = MovePtr<FboHelper>(new FboHelper(renderCtx, FRAMEBUFFER_WIDTH, FRAMEBUFFER_HEIGHT,
											 FRAMEBUFFER_FORMAT, actualSamples));

	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
}

void HelperInvocationDerivateCase::deinit (void)
{
	m_program.clear();
	m_fbo.clear();
}

static bool hasNeighborWithColor (const tcu::Surface& surface, int x, int y, tcu::RGBA color, tcu::RGBA threshold)
{
	const int	w	= surface.getWidth();
	const int	h	= surface.getHeight();

	for (int dx = -1; dx < 2; dx++)
	for (int dy = -1; dy < 2; dy++)
	{
		const IVec2	pos	= IVec2(x + dx, y + dy);

		if (dx == 0 && dy == 0)
			continue;

		if (de::inBounds(pos.x(), 0, w) && de::inBounds(pos.y(), 0, h))
		{
			const tcu::RGBA neighborColor = surface.getPixel(pos.x(), pos.y());

			if (tcu::compareThreshold(color, neighborColor, threshold))
				return true;
		}
		else
			return true; // Can't know for certain
	}

	return false;
}

static bool verifyHelperInvocationDerivate (TestLog& log, const tcu::Surface& result, bool isMultiSample)
{
	const tcu::RGBA		bgRef				(0, 0, 0, 255);
	const tcu::RGBA		fgRef				(0, 255, 0, 255);
	const tcu::RGBA		isBgThreshold		(1, isMultiSample ? 254 : 1, 0, 1);
	const tcu::RGBA		isFgThreshold		(1, isMultiSample ? 254 : 1, 255, 1);
	int					numInvalidPixels	= 0;
	int					numNonZeroDeriv		= 0;
	bool				renderedSomething	= false;

	for (int y = 0; y < result.getHeight(); ++y)
	{
		for (int x = 0; x < result.getWidth(); ++x)
		{
			const tcu::RGBA	resPix			= result.getPixel(x, y);
			const bool		isBg			= tcu::compareThreshold(resPix, bgRef, isBgThreshold);
			const bool		isFg			= tcu::compareThreshold(resPix, fgRef, isFgThreshold);
			const bool		nonZeroDeriv	= resPix.getBlue() > 0;
			const bool		neighborBg		= nonZeroDeriv ? hasNeighborWithColor(result, x, y, bgRef, isBgThreshold) : false;

			if (nonZeroDeriv)
				numNonZeroDeriv	+= 1;

			if ((!isBg && !isFg) ||							// Neither of valid colors (ignoring blue channel that has derivate)
				(nonZeroDeriv && !neighborBg && !isFg))		// Has non-zero derivate, but sample not at primitive edge or inside primitive
				numInvalidPixels += 1;

			if (isFg)
				renderedSomething = true;
		}
	}

	log << TestLog::Message << "Found " << numNonZeroDeriv << " pixels with non-zero derivate (neighbor sample has gl_HelperInvocation = true)" << TestLog::EndMessage;

	if (numInvalidPixels > 0)
	{
		log << TestLog::Image("Result", "Result image", result);
		log << TestLog::Message << "ERROR: Found " << numInvalidPixels << " invalid result pixels!" << TestLog::EndMessage;
		return false;
	}
	else if (!renderedSomething)
	{
		log << TestLog::Image("Result", "Result image", result);
		log << TestLog::Message << "ERROR: Result image was empty!" << TestLog::EndMessage;
		return false;
	}
	else
	{
		log << TestLog::Message << "All result pixels are valid" << TestLog::EndMessage;
		return true;
	}
}

HelperInvocationDerivateCase::IterateResult HelperInvocationDerivateCase::iterate (void)
{
	const glu::RenderContext&		renderCtx	= m_context.getRenderContext();
	const glw::Functions&			gl			= renderCtx.getFunctions();
	const string					sectionName	= string("Iteration ") + de::toString(m_iterNdx+1) + " / " + de::toString(m_numIters);
	const tcu::ScopedLogSection		section		(m_testCtx.getLog(), (string("Iter") + de::toString(m_iterNdx)), sectionName);
	de::Random						rnd			(deStringHash(getName()) ^ deInt32Hash(m_iterNdx));
	tcu::Surface					result		(FRAMEBUFFER_WIDTH, FRAMEBUFFER_HEIGHT);

	m_fbo->bindForRendering();
	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
	gl.clear(GL_COLOR_BUFFER_BIT);

	drawRandomPrimitives(renderCtx, m_program->getProgram(), m_primitiveType, 1, &rnd);

	m_fbo->readPixels(0, 0, result.getAccess());

	if (!verifyHelperInvocationDerivate(m_testCtx.getLog(), result, m_numSamples != 0))
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid pixels found");

	m_iterNdx += 1;
	return (m_iterNdx < m_numIters) ? CONTINUE : STOP;
}

} // anonymous

ShaderHelperInvocationTests::ShaderHelperInvocationTests (Context& context)
	: TestCaseGroup(context, "helper_invocation", "gl_HelperInvocation tests")
{
}

ShaderHelperInvocationTests::~ShaderHelperInvocationTests (void)
{
}

void ShaderHelperInvocationTests::init (void)
{
	static const struct
	{
		const char*		caseName;
		PrimitiveType	primType;
	} s_primTypes[] =
	{
		{ "triangles",		PRIMITIVETYPE_TRIANGLE		},
		{ "lines",			PRIMITIVETYPE_LINE			},
		{ "wide_lines",		PRIMITIVETYPE_WIDE_LINE		},
		{ "points",			PRIMITIVETYPE_POINT			},
		{ "wide_points",	PRIMITIVETYPE_WIDE_POINT	}
	};

	static const struct
	{
		const char*		suffix;
		int				numSamples;
	} s_sampleCounts[] =
	{
		{ "",					0				},
		{ "_4_samples",			4				},
		{ "_8_samples",			8				},
		{ "_max_samples",		NUM_SAMPLES_MAX	}
	};

	// value
	{
		tcu::TestCaseGroup* const valueGroup = new tcu::TestCaseGroup(m_testCtx, "value", "gl_HelperInvocation value in rendered pixels");
		addChild(valueGroup);

		for (int sampleCountNdx = 0; sampleCountNdx < DE_LENGTH_OF_ARRAY(s_sampleCounts); sampleCountNdx++)
		{
			for (int primTypeNdx = 0; primTypeNdx < DE_LENGTH_OF_ARRAY(s_primTypes); primTypeNdx++)
			{
				const string		name		= string(s_primTypes[primTypeNdx].caseName) + s_sampleCounts[sampleCountNdx].suffix;
				const PrimitiveType	primType	= s_primTypes[primTypeNdx].primType;
				const int			numSamples	= s_sampleCounts[sampleCountNdx].numSamples;

				valueGroup->addChild(new HelperInvocationValueCase(m_context, name.c_str(), "", primType, numSamples));
			}
		}
	}

	// derivate
	{
		tcu::TestCaseGroup* const derivateGroup = new tcu::TestCaseGroup(m_testCtx, "derivate", "Derivate of gl_HelperInvocation-dependent value");
		addChild(derivateGroup);

		for (int sampleCountNdx = 0; sampleCountNdx < DE_LENGTH_OF_ARRAY(s_sampleCounts); sampleCountNdx++)
		{
			for (int primTypeNdx = 0; primTypeNdx < DE_LENGTH_OF_ARRAY(s_primTypes); primTypeNdx++)
			{
				const string		name		= string(s_primTypes[primTypeNdx].caseName) + s_sampleCounts[sampleCountNdx].suffix;
				const PrimitiveType	primType	= s_primTypes[primTypeNdx].primType;
				const int			numSamples	= s_sampleCounts[sampleCountNdx].numSamples;

				derivateGroup->addChild(new HelperInvocationDerivateCase(m_context, (name + "_dfdx").c_str(),	"", primType, numSamples, "dFdx",	true));
				derivateGroup->addChild(new HelperInvocationDerivateCase(m_context, (name + "_dfdy").c_str(),	"", primType, numSamples, "dFdy",	true));
				derivateGroup->addChild(new HelperInvocationDerivateCase(m_context, (name + "_fwidth").c_str(),	"", primType, numSamples, "fwidth",	false));
			}
		}
	}
}

} // Functional
} // gles31
} // deqp
